//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		MenuItem.cs
//
//	Author:		MJP
//
//	Date:		04/08/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.Drawing.Design;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CPXMenu
{
	/// <summary>
	/// Base class for all all objects in Menu project.  Handles some
	/// basic functionality common to all types.
	/// </summary>
    [TypeConverter(typeof(ExpandableObjectConverter))]
    [CanHaveChildren(false)]
    [IconKey(IconKeyAttribute.DefaultIconKey)]
    [Selectable(false)]
    public abstract class MenuItem : IContentOwner
    {
        private string name;
        private Point absolutePosition;
        private Point dimensions;
        private List<MenuItem> children = new List<MenuItem>();
        private MenuItem parent;        
        private ulong id;
        private bool designMode = false;
        private bool initialized = false;
        private bool focused = false;
        private bool selected = false;
        private int index = 0;
        private List<ImageState> imageStates = new List<ImageState>();
        private int imageStateIndex = -1;
        private int prevImageStateIndex = -1;
        private float imageTransitionTime = 0;
        private List<TextState> textStates = new List<TextState>();
        private int textStateIndex = -1;
        private int prevTextStateIndex = -1;
        private float textTransitionTime = 0;
        private ulong parentID = 0;
        private MenuProject parentProject;

#if DEBUG
        private int cloneCount = 0;
#endif

        protected GraphicsDevice graphicsDevice;
        protected ContentManager contentManager;        

		/// <summary>
		/// Gets or sets the name of the MenuItem, unique among a MenuProject
		/// </summary>
        [Browsable(true)]
        [Description("The name of the menu item")]
        [Category("Design")]
        public string Name
        {
            get { return name; }
            set 
            {                
                if (string.IsNullOrEmpty(name))
                    throw new Exception("Name must have at least 1 character");

                if (initialized)
                {
                    if (VerifyNameIsUnique(value))
                    {
                        string error;
                        if (VerifyName(value, out error))
                        {
                            name = value;
                            OnNameChanged();
                        }
                        else
                            throw new Exception("Invalid name specified: " + error);
                    }
                    else
                        throw new Exception("Name must be unique among all MenuItems");
                }
                else
                    name = value;
            }
        }

		/// <summary>
		/// Gets or sets the position of the item relative to its parent
		/// </summary>
        [Browsable(true)]
        [Description("The position of the menu item")]
        [Category("Appearance")]
        [XmlIgnore]
        public virtual Point Position
        {
            get 
            {
                return SubtractPoint(absolutePosition, GetParentAbsolutePosition());
            }
            set 
            {
                AbsolutePosition = AddPoint(GetParentAbsolutePosition(), value);
            }
        }

		/// <summary>
		/// Gets or sets the width and height of the item
		/// </summary>
        [Browsable(true)]
        [Description("The width and height of the menu item")]
        [Category("Appearance")]
        public virtual Point Dimensions
        {
            get { return dimensions; }
            set 
            {
                if (value.X <= 0 || value.Y <= 0)
                    throw new Exception("Invalid dimensions specified, must be greater than 0");

                dimensions = value;
                OnDimensionsChanged();

                foreach (TextState textState in textStates)
                    textState.Dimensions = dimensions;
            }
        }

		/// <summary>
		/// Gets the center point of the item, in absolute coordinates
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public Point CenterPoint
        {
            get
            {
                Point center = new Point();
                center.X = (int)(AbsolutePosition.X + Dimensions.X / 2.0f);
                center.Y = (int)(AbsolutePosition.Y + Dimensions.Y / 2.0f);
                return center;
            }
        }


		/// <summary>
		/// Gets or sets a value indicating the item's index in the parent's children collection
		/// </summary>
        [Browsable(true)]
        [Description("The index of the item in the parent's children collection.  Affects draw order, and selection order")]
        [Category("Behavior")]
        public virtual int Index
        {
            get { return index; }
            set
            {
                if (Initialized)
                {
                    if (parent != null)
                        parent.SetChildIndex(this, index, value);
                    else
                        Debug.Assert(false);
                }
                else
                    index = value;
            }
        }

		/// <summary>
		/// Gets or sets the integer ID of the item, unique among the project
		/// </summary>
        [Browsable(false)]
        public ulong ID
        {
            get { return id; }
            set { id = value; }
        }

        /// <summary>
        /// Gets or sets the integer ID of the parent item
        /// </summary>
        [Browsable(false)]
        public ulong ParentID
        {
            get { return parentID; }
            set
            {
                parentID = value;
                if (Initialized)
                    parent = GetParentProject().GetMenuItemByID<MenuItem>(parentID);
            }
        }

		/// <summary>
		/// Gets the parent MenuProject of the item
		/// </summary>
		[Browsable(false)]
		[XmlIgnore]
		public MenuProject ParentProject
		{
			get { return GetParentProject(); }
		}

		/// <summary>
		/// Gets a collection of all child items
		/// </summary>
        [Browsable(false)]
        public List<MenuItem> Children
        {
            get { return children; }
            set { children = value; }
        }

		/// <summary>
		/// Gets the parent item
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public MenuItem Parent
        {
            get { return parent; }
        }

		/// <summary>
		/// Gets or sets the absolute position of the item
		/// </summary>
        [Browsable(false)]
        public virtual Point AbsolutePosition
        {
            get { return absolutePosition; }
            set 
            {
                Point difference = SubtractPoint(value, absolutePosition);

                absolutePosition = value;
                
                if (Initialized)
                    foreach (MenuItem child in children)
                        child.AbsolutePosition = AddPoint(child.absolutePosition, difference);
            }
        }

		/// <summary>
		/// Gets a bounding rectangle of the item, in absolute coordinates
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public Rectangle BoundingRect
        {
            get { return new Rectangle(AbsolutePosition.X, AbsolutePosition.Y, Dimensions.X, Dimensions.Y); }
        }

		/// <summary>
		/// Gets the list of ImageStates for the item
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public List<ImageState> ImageStates
        {
            get { return imageStates; }
        }

        /// <summary>
        /// Gets the list of TestStates for the item
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public List<TextState> TextStates
        {
            get { return textStates; }
        }        

		/// <summary>
		/// Gets a value indicating whether this item has been initialized
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool Initialized
        {
            get { return initialized; }
        }

		/// <summary>
		/// Gets or sets a value indicating whether the item has user focus
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool Focused
        {
            get { return focused; }            
        }

        /// <summary>
        /// Gets or sets a value indicating whether the item has user focus
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool Selected
        {
            get { return selected; }
        }

		/// <summary>
		/// Gets the current ImageState according to the ImageStateIndex
		/// </summary>
		[Browsable(false)]
		[XmlIgnore]
		public ImageState CurrentImageState
		{
			get
			{
				if (imageStateIndex >= 0 && imageStateIndex < imageStates.Count)
					return imageStates[imageStateIndex];
				else
					return null;
			}
		}

		/// <summary>
		/// Gets the current TextState according to the TextStateIndex
		/// </summary>
		[Browsable(false)]
		[XmlIgnore]
		public TextState CurrentTextState
		{
			get
			{
				if (textStateIndex >= 0 && textStateIndex < textStates.Count)
					return textStates[textStateIndex];
				else
					return null;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the item can have children
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool CanHaveChildren
        {
            get { return MenuItem.TypeCanHaveChildren(this.GetType()); }
        }

		/// <summary>
		/// Gets a string that can be used as a key in the global icon ImageList		
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public string IconKey
        {
            get { return MenuItem.GetIconKey(this.GetType()); }
        }

		/// <summary>
		/// Gets a Type indicating the MenuItem Type that can be a child of this item,
		/// if one is specified. null otherwise.
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public Type ChildType
        {
            get { return MenuItem.GetChildType(this.GetType()); }
        }

		/// <summary>
		/// Gets a Type indicating the MenuItem Type that can be a parent of this item,
		/// if one is specified. null otherwise.
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public Type ParentType
        {
            get { return MenuItem.GetParentType(this.GetType()); }
        }

		/// <summary>
		/// Gets a value indicating whether this Type can be moved at design-time
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool IsMovable
        {
            get { return MenuItem.TypeIsMoveable(this.GetType()); }
        }

		/// <summary>
		/// Gets a value indicating whether this Type can be sized at design-time
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool IsSizeable
        {
            get { return MenuItem.TypeIsSizeable(this.GetType()); }
        }

		/// <summary>
		/// Gets a value indicating whether this Type cane be copy and pasted at
		/// design-time
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool CanCopyPaste
        {
            get { return MenuItem.CanCopyPasteType(this.GetType()); }
        }

		/// <summary>
		/// Gets the category that this Type will be placed into in the Toolbox
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public string Category
        {
            get { return MenuItem.TypeCategory(this.GetType()); }
        }

        /// <summary>
        /// Gets the description for this item's Type
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public string Description
        {
            get { return MenuItem.TypeDescription(this.GetType()); }
        }

        /// <summary>
        /// Gets a value indicating whether this item's Type can have user focus
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool CanHaveFocus
        {
            get { return MenuItem.TypeCanHaveFocus(this.GetType()); }
        }

        /// <summary>
        /// Gets a value indicating whether this item's Type can be selected
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public bool IsSelectable
        {
            get { return MenuItem.TypeIsSelectable(this.GetType()); }
        }

		/// <summary>
		/// Gets or sets a value indicating whether this item is being edited
		/// in the editor.
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        internal bool DesignMode
        {
            get { return designMode; }
            set { designMode = value; }
        }

		/// <summary>
		/// Gets or sets the index of the ImageState that should be drawn
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        internal int ImageStateIndex
        {
            get { return imageStateIndex; }
            set 
            {
                if (value == imageStateIndex)
                    return;

                prevImageStateIndex = imageStateIndex;
                imageStateIndex = value;
                imageTransitionTime = 0;
            }
        }

        /// <summary>
        /// Gets or sets the index of the TextState that should be drawn
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        internal int TextStateIndex
        {
            get { return textStateIndex; }
            set 
            {
                if (value == textStateIndex)
                    return;

                prevTextStateIndex = textStateIndex;
                textStateIndex = value;
                textTransitionTime = 0;
            }
        }

        /// <summary>
        /// Gets the ContentManager for the item
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public ContentManager Content
        {
            get { return contentManager; }
        }

        /// <summary>
        /// Gets the item that owns the content
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public MenuItem OwnerItem
        {
            get { return this; }
        }

		/// <summary>
		/// Constructor
		/// </summary>
        public MenuItem()
        {
            name = this.GetType().Name;
        }          

		/// <summary>
		/// Creates a clone of the item.  Child items are also cloned.
		/// </summary>
		/// <returns></returns>
        public virtual MenuItem Clone()
        {            
            MenuItem clone = (MenuItem)this.MemberwiseClone();

            clone.NameChanged = null;

#if DEBUG
            clone.cloneCount++;
#endif

            clone.imageStates = new List<ImageState>(imageStates.Count);
            // foreach (ImageState imageState in imageStates)
            //    clone.imageStates.Add(imageState.Clone());

            clone.textStates = new List<TextState>(textStates.Count);
            // foreach (TextState textState in textStates)
            //    clone.textStates.Add(textState.Clone());

            clone.children = new List<MenuItem>();
            foreach (MenuItem child in children)
                clone.children.Add(child.Clone());            

            return clone;
        }

		/// <summary>
		/// Verifies that a name is unique among he project
		/// </summary>
		/// <param name="newName">The name to test</param>
		/// <returns>true if the name is unique, false otherwise</returns>
        public bool VerifyNameIsUnique(string newName)
        {
            MenuItem item = GetParentProject().GetMenuItemByName<MenuItem>(newName);
            return item == null || item == this;
        }

		/// <summary>
		/// ToString override
		/// </summary>
		/// <returns>The name of the item</returns>
        public override string ToString()
        {
            return name;
        }

		/// <summary>
		/// Gets the parent project of the item
		/// </summary>
		/// <returns>The parent project</returns>
        public MenuProject GetParentProject()
        {
            if (this is MenuProject)
                return (MenuProject)this;
            else
                return parentProject;
        }

		/// <summary>
		/// Gets an array with the full parent hierarchy of the item
		/// </summary>
		/// <returns></returns>
        public MenuItem[] GetAllParents()
        {
            List<MenuItem> parents = new List<MenuItem>();
            MenuItem parent = Parent;
            while (parent != null)
            {
                parents.Add(parent);
                parent = parent.Parent;
            }

            return parents.ToArray();
        }

		/// <summary>
		/// Tests whether the point intersects with the item's bounding rectangle,
		/// and if it does tests whether it also intersects with child items.
		/// </summary>
		/// <param name="cursorPos">The position of the cursor</param>
		/// <returns>The top-most item that the point intersects</returns>
        public virtual MenuItem ClickTest(Point cursorPos)
        {            
            if (BoundingRect.Contains(cursorPos))
            {
                foreach (MenuItem child in children)
                {
                    MenuItem test = child.ClickTest(cursorPos);
                    if (test != null)
                        return test;
                }

                return this;
            }
            else
                return null;
        }

		/// <summary>
		/// Adds a child itme
		/// </summary>
		/// <param name="child">The child to add</param>
		internal virtual void AddChild(MenuItem child)
		{
			Debug.Assert(TypeCanHaveChildren(this.GetType()));
			Debug.Assert(!children.Contains(child));

			Point position = child.Position;
			children.Add(child);
			child.parent = this;
            child.parentID = id;
			child.index = children.Count - 1;
			child.Position = position;
			child.OnAddedToParent();
		}

		/// <summary>
		/// Removes the specified child
		/// </summary>
		/// <param name="child">The child item to remove</param>
		internal virtual void RemoveChild(MenuItem child)
		{
			Debug.Assert(TypeCanHaveChildren(this.GetType()));

			if (children.Contains(child))
			{
				children.Remove(child);
				child.parent = null;
				child.OnRemovedFromParent();

				for (int i = 0; i < children.Count; i++)
					children[i].index = i;
			}
			else
				Debug.Assert(false);
		}      
		
		/// <summary>
		/// Initializes the item
		/// </summary>
		/// <param name="contentManager">The ContentManager to use for content loading</param>
		/// <param name="graphicsDevice">The GraphicsDevice to use</param>
        internal virtual void Initialize(MenuProject parentProject, ContentManager contentManager, GraphicsDevice graphicsDevice)
        {
            this.contentManager = contentManager;
            this.graphicsDevice = graphicsDevice;
            this.parentProject = parentProject;

            if (parentID != 0)
                parent = parentProject.GetMenuItemByID<MenuItem>(parentID);

            foreach (ImageState imageState in imageStates)
                imageState.Initialize(contentManager, this);

            foreach (TextState textState in textStates)
                textState.Initialize(contentManager, dimensions, this);

            initialized = true;
        }

		/// <summary>
		/// Updates the item
		/// </summary>
		/// <param name="dt">The time delta</param>
        internal virtual void Update(float dt)
        {
            foreach (MenuItem child in children)
                child.Update(dt);
        }

		/// <summary>
		/// Renders the item
		/// </summary>
		/// <param name="dt">The time delta</param>
		/// <param name="spriteBatch">The shared SpriteBatch to use</param>
        internal virtual void Render(float dt, SpriteBatch spriteBatch, RoundLineManager lineManager, ReadOnlyCollection<MenuItem> selectedItems, MenuItem highlightedItem)
        {
            Rectangle oldScissor = graphicsDevice.ScissorRectangle; 
            Rectangle newRect = Rectangle.Intersect(BoundingRect, oldScissor);
            graphicsDevice.ScissorRectangle = newRect;

            bool selected = selectedItems != null && selectedItems.Contains(this);            
            bool highlighted = highlightedItem == this;
            RenderThis(dt, spriteBatch, lineManager, selected, highlighted);

            if (selected || highlighted)
            {
                Color drawColor = Color.Yellow;
                if (highlighted)
                    drawColor = Color.Blue;

                MenuProject project = GetParentProject();
                Matrix projection = Matrix.CreateOrthographicOffCenter(0, project.Dimensions.X, 0, project.Dimensions.Y, 1, 100);
                float projectHeight = project.Dimensions.Y;

                List<RoundLine> lines = new List<RoundLine>();
                Vector2 topLeft = new Vector2(AbsolutePosition.X - 1, projectHeight - AbsolutePosition.Y + 1);
                Vector2 topRight = new Vector2(AbsolutePosition.X + Dimensions.X, projectHeight - AbsolutePosition.Y + 1);
                Vector2 bottomRight = new Vector2(AbsolutePosition.X + Dimensions.X, projectHeight - (AbsolutePosition.Y + Dimensions.Y));
                Vector2 bottomLeft = new Vector2(AbsolutePosition.X - 1, projectHeight - (AbsolutePosition.Y + Dimensions.Y));

                lines.Add(new RoundLine(topLeft, topRight));
                lines.Add(new RoundLine(topRight, bottomRight));
                lines.Add(new RoundLine(bottomRight, bottomLeft));
                lines.Add(new RoundLine(bottomLeft, topLeft));
                lineManager.Draw(lines, 3, drawColor, projection, 0.0f, "Standard");
            }

            RenderChildren(dt, spriteBatch, lineManager, selectedItems, highlightedItem);

            graphicsDevice.ScissorRectangle = oldScissor;
        }

        /// <summary>
        /// Called when an item should actually render itself
        /// </summary>
        /// <param name="dt">time delta</param>
        /// <param name="spriteBatch">shared SpriteBatch</param>
        internal virtual void RenderThis(float dt, SpriteBatch spriteBatch, RoundLineManager lineManager, bool selected, bool highlighted)
        {
            if (imageStateIndex >= 0 && imageStateIndex < imageStates.Count)
            {
                float alpha = 1.0f;
                ImageState imageState = imageStates[imageStateIndex];
                if (!DesignMode && prevImageStateIndex >= 0 && prevImageStateIndex < imageStates.Count) 
                {
                    float totalTransitionTime = MathHelper.Min(imageStates[prevImageStateIndex].TransitionTime, imageStates[imageStateIndex].TransitionTime);

                    if (imageTransitionTime < totalTransitionTime)
                    {
                        alpha = imageTransitionTime / totalTransitionTime;
                        imageTransitionTime += dt;
                    }
                }

				imageState.Render(dt, spriteBatch, DetermineImageDrawRect(), 1.0f);

                if (alpha < 1.0f)
                    imageStates[prevImageStateIndex].Render(dt, spriteBatch, DetermineImageDrawRect(), 1 - alpha);
            }

            if (textStateIndex >= 0 && textStateIndex < textStates.Count)
            {
                float alpha = 1.0f;
                TextState textState = textStates[textStateIndex];
                if (!DesignMode && prevTextStateIndex >= 0 && prevTextStateIndex < textStates.Count)
                {
                    float totalTransitionTime = MathHelper.Min(textStates[prevTextStateIndex].TransitionTime, textStates[textStateIndex].TransitionTime);

                    if (textTransitionTime < totalTransitionTime)
                    {
                        alpha = textTransitionTime / totalTransitionTime;
                        textTransitionTime += dt;
                    }
                }

				textState.Render(spriteBatch, DetermineTextDrawRect(), 1.0f);

                if (alpha < 1.0f)
                    textStates[prevTextStateIndex].Render(spriteBatch, DetermineTextDrawRect(), 1 - alpha);
            }
        }

        /// <summary>
        /// Called when an item should render its children
        /// </summary>
        /// <param name="dt">time delta</param>
        /// <param name="spriteBatch">shared SpriteBatch</param>
        internal virtual void RenderChildren(float dt, SpriteBatch spriteBatch, RoundLineManager lineManager, ReadOnlyCollection<MenuItem> selectedItems, MenuItem highlightedItem)
        {
            foreach (MenuItem child in children)
                child.Render(dt, spriteBatch, lineManager, selectedItems, highlightedItem);
        }

		/// <summary>
		/// Called when an item is added to the project
		/// </summary>
        internal virtual void OnAddedToProject()
        {

        }

		/// <summary>
		/// Called when an item is removed from a project
		/// </summary>
        internal virtual void OnRemovedFromProject()
        {

        }

        /// <summary>
        /// Called when user focus is acquired
        /// </summary>
        internal virtual void OnFocusAcquired()
        {
            focused = true;

            if (FocusAquired != null)
                FocusAquired(this);            
        }

        /// <summary>
        /// Called when user focus is lost
        /// </summary>
        internal virtual void OnFocusLost()
        {            
            focused = false;            

            if (FocusLost != null)
                FocusLost(this);

            OnSelectionLost();
        }

        /// <summary>
        /// Called when an item is selected
        /// </summary>
        internal virtual void OnSelectionAcquired()
        {
            selected = true;

            if (SelectionAcquired != null)
                SelectionAcquired(this);
        }

        /// <summary>
        /// Called when an item is no longer selected
        /// </summary>
        internal virtual void OnSelectionLost()
        {
            bool clicked = focused;

            selected = false;

            if (SelectionLost != null)
                SelectionLost(this);

            if (Focused)
                OnClicked();
        }

        /// <summary>
        /// Called when imported content owned by this item is removed from the project
        /// </summary>
        internal virtual void OnOwnedContentRemoved(ImportedContentItem contentItem)
        {
            foreach (ImageState imageState in imageStates)
                imageState.OnOwnedContentRemoved(contentItem);

            foreach (TextState textState in textStates)
                textState.OnOwnedContentRemoved(contentItem);
        }

		/// <summary>
		/// Determines the rectangle to which the current ImageState should be fit to
		/// </summary>
		/// <returns>the rectangle to which the current ImageState should be fit to</returns>
		protected virtual Rectangle DetermineImageDrawRect()
		{
			return BoundingRect;
		}

		/// <summary>
		/// Determines the rectangle to which the current TextState should be fit to
		/// </summary>
		/// <returns>the rectangle to which the current TextState should be fit to</returns>
		protected virtual Rectangle DetermineTextDrawRect()
		{
			return BoundingRect;
		}

        /// <summary>
        /// Called when the item acquires and loses selection while retaining focus.
        /// In other words, the user presses the selection button and releases it
        /// without moving to another item.
        /// </summary>
        protected virtual void OnClicked()
        {
            if (Clicked != null)
                Clicked(this);
        }

		/// <summary>
		/// Called when an item is added to its parent item
		/// </summary>
        protected virtual void OnAddedToParent()
        {
            
        }

		/// <summary>
		/// Called when an item is removed from its parent item
		/// </summary>
        protected virtual void OnRemovedFromParent()
        {

        }

		/// <summary>
		/// Called when an item's dimensions change
		/// </summary>
        protected virtual void OnDimensionsChanged()
        {
            if (DimensionsChanged != null)
                DimensionsChanged(this);
        }

		/// <summary>
		/// Called when the item's children are reordered
		/// as the result of Index being changed.
		/// </summary>
        protected virtual void OnChildrenReordered()
        {
            if (ChildrenReordered != null)
                ChildrenReordered(this);
        }

		/// <summary>
		/// Gets the absolute position of the parent
		/// </summary>
		/// <returns>The parent's absolute position</returns>
        protected Point GetParentAbsolutePosition()
        {
            if (parent == null)
                return new Point();
            else
                return parent.absolutePosition;
        }          		

		/// <summary>
		/// Called when an item's name is changed
		/// </summary>
        protected virtual void OnNameChanged()
        {
            if (NameChanged != null)
                NameChanged(this);
        }

        /// <summary>
        /// Allows derivative types to verify that a name is acceptable
        /// </summary>
        /// <param name="newName">The new name to verify</param>
        protected virtual bool VerifyName(string newName, out string error)
        {
            error = string.Empty;
            return true;
        }

		/// <summary>
		/// Changes the Index of a child item, and reorders the children in the collection
		/// </summary>
		/// <param name="child">The child whose index is being modified</param>
		/// <param name="oldIndex">The old index of the item</param>
		/// <param name="newIndex">The new index of the item</param>
        private void SetChildIndex(MenuItem child, int oldIndex, int newIndex)
        {
            Debug.Assert(children.Contains(child));
            Debug.Assert(children[oldIndex] == child);

            if (oldIndex == newIndex)
                return;

            if (newIndex < 0)
                throw new Exception("The specified index is invalid. The index must be between 0 and " + (children.Count - 1).ToString() + ".");

            if (newIndex >= children.Count)
                throw new Exception("The specified index is too large. The max valid index is " + (children.Count - 1).ToString() + ".");

            children.Remove(child);
            children.Insert(newIndex, child);
            for (int i = 0; i < children.Count; i++)
                children[i].index = i;

            OnChildrenReordered();
        }        		        

        // Events

		/// <summary>
		/// Called when an item's name is changed
		/// </summary>
        public event MenuItemDelegate NameChanged;

        /// <summary>
        /// Called when focus is acquired
        /// </summary>
        public event MenuItemDelegate FocusAquired;

        /// <summary>
        /// Called when focus is lost
        /// </summary>
        public event MenuItemDelegate FocusLost;

        /// <summary>
        /// Called when the cursor is pressed on the item
        /// </summary>
        public event MenuItemDelegate SelectionAcquired;

        /// <summary>
        /// Called when the cursor is released on the item
        /// </summary>
        public event MenuItemDelegate SelectionLost;

        /// <summary>
        /// Called when the item is clicked
        /// </summary>
        public event MenuItemDelegate Clicked;

        /// <summary>
        /// Called when the dimensions are modified
        /// </summary>
        public event MenuItemDelegate DimensionsChanged;

        /// <summary>
        /// Called when the children of the item are reordered
        /// </summary>
        public event MenuItemDelegate ChildrenReordered;


        /// <summary>
        /// Gets an array of all MenuItem derivatives in the assembly
        /// </summary>
        /// <returns>The array of MenuItem types</returns>
        public static Type[] GetMenuItemTypes()
        {
            List<Type> menuItemTypes = new List<Type>();

            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "CPXMenu";

            Assembly assembly = Assembly.Load(assemblyName);
            List<Type> objectTypes = new List<Type>();
            foreach (Type type in assembly.GetTypes())
                if (IsMenuItemType(type))
                    menuItemTypes.Add(type);

            return menuItemTypes.ToArray();
        }

        /// <summary>
        /// Returns true of the type is a MenuItem derivative
        /// </summary>
        /// <param name="type">The type to check</param>
        /// <returns>True if the type is a MenuItem</returns>
        public static bool IsMenuItemType(Type type)
        {
            if (type == typeof(MenuItem))
                return true;
            else
            {
                if (type.BaseType != null)
                    return IsMenuItemType(type.BaseType);
                else
                    return false;
            }
        }

		/// <summary>
		/// Returns true if the Type represented by the string name is
		/// a MenuItem derivative
		/// </summary>
		/// <param name="typeName">The name of the Type to check</param>
		/// <returns>True if the Type is a MenuItem derivative</returns>
        public static bool IsMenuItemType(string typeName)
        {
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "CPXMenu";

            Assembly assembly = Assembly.Load(assemblyName);
            Type type = assembly.GetType(typeName, false);
            if (type != null)
                return IsMenuItemType(type);
            else
                return false;
        }

		/// <summary>
		/// Returns true if the specified Type can have children
		/// </summary>
		/// <param name="menuItemType">The Type to check</param>
		/// <returns>True if the Type can have children</returns>
        public static bool TypeCanHaveChildren(Type menuItemType)
        {
            CanHaveChildrenAttribute attrib = GetCustomAttribute<CanHaveChildrenAttribute>(menuItemType);
            if (attrib != null && attrib.CanHaveChildren)
                return true;
            else
                return false;
        }

		/// <summary>
		/// Gets the valid child Type of this Type, if one is specified 
		/// </summary>
		/// <param name="menuItemType">The Type to query</param>
		/// <returns>The child Type, if one is specified</returns>
        public static Type GetChildType(Type menuItemType)
        {
            ChildTypeAttribute attrib = GetCustomAttribute<ChildTypeAttribute>(menuItemType);
            if (attrib != null)
                return attrib.ChildType;
            else
                return null;
        }

		/// <summary>
		/// Gets the string key of the Type's icon in the global image list
		/// </summary>
		/// <param name="menuItemType">The Type to query</param>
		/// <returns>The icon key</returns>
        public static string GetIconKey(Type menuItemType)
        {
            IconKeyAttribute attrib = GetCustomAttribute<IconKeyAttribute>(menuItemType);
            if (attrib != null)
                return attrib.IconKey;
            else
                return IconKeyAttribute.DefaultIconKey;
        }

		/// <summary>
		/// Gets the valid child Type of this Type, if one is specified 
		/// </summary>
		/// <param name="menuItemType">The Type to query</param>
		/// <returns>The child Type, if one is specified</returns>
        public static Type GetParentType(Type menuItemType)
        {
            ParentTypeAttribute attrib = GetCustomAttribute<ParentTypeAttribute>(menuItemType);
            if (attrib != null)
                return attrib.ParentType;
            else
                return null;
        }

		/// <summary>
		/// Determines whether the specifid child/parent Type combination is valid
		/// </summary>
		/// <param name="parentType">The parent Type to query</param>
		/// <param name="childType">The child Type to query</param>
		/// <returns>True if the combination is valid. False otherwise.</returns>
        public static bool IsValidParentChildCombination(Type parentType, Type childType)
        {
            if (TypeCanHaveChildren(parentType))
            {
                Type validChildType = GetChildType(parentType);
                if (validChildType == null || validChildType == childType)
                {
                    Type validParentType = GetParentType(childType);
                    if (validParentType == null || validParentType == parentType)
                        return true;
                }
            }

            return false;
        }

		/// <summary>
		/// Determines whether a type is moveable at design-time
		/// </summary>
		/// <param name="menuItemType">The Type to query</param>
		/// <returns>True if the Type is moveable, false otherwise</returns>
        public static bool TypeIsMoveable(Type menuItemType)
        {
            MovableAttribute attrib = GetCustomAttribute<MovableAttribute>(menuItemType);
            if (attrib != null && attrib.Movable)
                return true;
            else
                return false;
        }

		/// <summary>
		/// Determines whether a type is sizeable at design-time
		/// </summary>
		/// <param name="menuItemType">The Type to query</param>
		/// <returns>True if the Type is sizeable, false otherwise</returns>
        public static bool TypeIsSizeable(Type menuItemType)
        {
            SizeableAttribute attrib = GetCustomAttribute<SizeableAttribute>(menuItemType);
            if (attrib != null && attrib.Sizeable)
                return true;
            else
                return false;
        }

		/// <summary>
		/// Determines whether a type can be copy/pasted at design-time
		/// </summary>
		/// <param name="menuItemType">The Type to query</param>
		/// <returns>True if the Type can be copy/pasted, false otherwise</returns>
        public static bool CanCopyPasteType(Type menuItemType)
        {
            CanCopyPasteAttribute attrib = GetCustomAttribute<CanCopyPasteAttribute>(menuItemType);
            if (attrib != null && attrib.CanCopyPaste)
                return true;
            else
                return false;
        }

		/// <summary>
		/// Gets the category that the MenuItem Type belongs to
		/// </summary>
		/// <param name="menuItemType">The Type to query</param>
		/// <returns>The category of the Type</returns>
        public static string TypeCategory(Type menuItemType)
        {
            ItemCategoryAttribute attrib = GetCustomAttribute<ItemCategoryAttribute>(menuItemType);
            if (attrib == null)
                return ItemCategoryAttribute.DefaultCategory;
            else
                return attrib.Category;
        }

        /// <summary>
        /// Gets the description for a MenuItem Type
        /// </summary>
        /// <param name="menuItemType">The Type to query</param>
        /// <returns>The Type description</returns>
        public static string TypeDescription(Type menuItemType)
        {
            DescriptionAttribute attrib = GetCustomAttribute<DescriptionAttribute>(menuItemType);
            if (attrib == null)
                return "";
            else
                return attrib.Description;
        }

        /// <summary>
        /// Gets a value indicating whether the Type can have user focus
        /// </summary>
        /// <param name="menuItemType">The Type to query</param>
        /// <returns>a value indicating whether the Type can have user focus</returns>
        public static bool TypeCanHaveFocus(Type menuItemType)
        {
            CanHaveFocusAttribute attrib = GetCustomAttribute<CanHaveFocusAttribute>(menuItemType);
            if (attrib == null)
                return false;
            else
                return attrib.CanHaveFocus;
        }

        /// <summary>
        /// Gets a value indicating whether the Type can be selected
        /// </summary>
        /// <param name="menuItemType">The Type to query</param>
        /// <returns>a value indicating whether the Type can be selected</returns>
        public static bool TypeIsSelectable(Type menuItemType)
        {
            SelectableAttribute attrib = GetCustomAttribute<SelectableAttribute>(menuItemType);
            if (attrib == null)
                return false;
            else
                return attrib.Selectable;
        }

        /// <summary>
        /// Adds two points together
        /// </summary>
        /// <param name="leftHand">Left-hand side of the addition</param>
        /// <param name="rightHand">Right-hand side of the addition</param>
        /// <returns>The result of addition</returns>
        public static Point AddPoint(Point leftHand, Point rightHand)
        {
            Point result = new Point();
            result.X = leftHand.X + rightHand.X;
            result.Y = leftHand.Y + rightHand.Y;
            return result;
        }

        /// <summary>
        /// Subtracts two points
        /// </summary>
        /// <param name="leftHand">Left-hand side of the subtraction</param>
        /// <param name="rightHand">Right-hand side of the subtraction</param>
        /// <returns>The result of the subtraction</returns>
        public static Point SubtractPoint(Point leftHand, Point rightHand)
        {
            Point result = new Point();
            result.X = leftHand.X - rightHand.X;
            result.Y = leftHand.Y - rightHand.Y;
            return result;
        }

        /// <summary>
        /// Gets a custom Attribute assigned to this MenuItem's type
        /// </summary>
        /// <typeparam name="TAttribute">The Attribute to retrieve</typeparam>
        /// <returns>The specified attribute, if applied to this type.  null otherwise.</returns>
        private static TAttribute GetCustomAttribute<TAttribute>(Type menuItemType) where TAttribute : Attribute
        {
            Debug.Assert(IsMenuItemType(menuItemType));

            object[] attributes = menuItemType.GetCustomAttributes(false);
            foreach (object attribute in attributes)
                if (attribute is TAttribute)
                    return attribute as TAttribute;
            return null;
        }  
    }

    public delegate void MenuItemDelegate(MenuItem item);

    public delegate void MenuItemCursorDelegate(MenuItem item, Point cursorPos);
}
